#!/bin/bash

#
# Copyright(c) 2012-2021 Intel Corporation
# SPDX-License-Identifier: BSD-3-Clause
#

export NAME=`basename $0`

export RESTORE='\033[0m'
export RED='\033[00;31m'
export GREEN='\033[00;32m'
export YELLOW='\033[00;33m'

if [ -z $TESTS_DIR ] ; then
    export TESTS_DIR="$(dirname $0)"
fi

export LOGS_FOLDER="$TESTS_DIR/logs"

if [ -z "$TEST_LOG_DIR" ] ; then
    export TEST_LOG_DIR="$TESTS_DIR/logs"
fi
export TEST_TIME=""

check_if_root_or_exit() {
    if [[ $EUID -ne 0 ]] ; then
        echo "You need to have root privileges to launch DVT tests"
        exit 1
    fi
}

resolve_path() {
    local BY_ID_DIR="/dev/disk/by-id"
    local BY_ID_LINKS=$(ls $BY_ID_DIR)

    for BY_ID_PATH in $BY_ID_LINKS
    do
        FULL_PATH="${BY_ID_DIR}/${BY_ID_PATH}"
        if [[ "$(realpath $FULL_PATH)" -ef "$(realpath $DEVICE)" ]]
        then
            DEVICE=$FULL_PATH
            break
        fi
    done
}

parse_args() {
    while [ -n "$1" ] ; do

        case $1 in
            -a | --all )        export TEST_MODE="all"
                                ;;
            -x | --dir )        shift
                                export TEST_MODE="dir"
                                TEST_MODE_DIR="$1"
                                ;;
            -b | --bvt )        export TEST_MODE="bvt"
                                ;;
            -f | --atomic )     export TEST_MODE="atomic"
                                ;;
            -n | --nightly )    export TEST_MODE="nightly"
                                ;;
            -s | --sanity )     export TEST_MODE="sanity"
                                ;;
            -i | --ignore )     export IGNORE_WARNINGS="1"
                                ;;
            -c | --cache )      shift
                                DEVICE="$1"
                                resolve_path
                                CACHE_DEVICE=$DEVICE
                                ;;
            -d | --core )       shift
                                DEVICE="$1"
                                resolve_path
                                CORE_DEVICE=$DEVICE
                                ;;
            * )                 echo "Unrecognized option"
                                usage
                                exit 1
        esac
        shift
    done
}

usage() {
    echo "example: ./${NAME} -b"
    echo "  -a | --all - run all TESTS"
    echo "  -b | --bvt - run BVT"
    echo "  -n | --nightly - run nightly"
    echo "  -s | --sanity - sanity check"
    echo "  -i | --ignore - ignore config warnings"
    echo "  -c | --cache - override cache device from settings file"
    echo "  -d | --core - override core device from settings file"
    echo "  -x | --dir <dir> - performs tests for specified directory (e.g. --dir basic)"
}

load_cas_lib() {
    if [ -z $CAS_CONFIGURATION_LOADED ] ; then
        . $TESTS_DIR/cas_config
    fi
    if [ -z $CAS_FUNCTIONS_LOADED ] ; then
        . $TESTS_DIR/cas_functions
    fi
    if [ -z $CAS_OPTIONS_LOADED ] ; then
        . $TESTS_DIR/cas_options
    fi
    if [ -f ~/cas_local_config ] ; then
        . ~/cas_local_config
        echo "--- Using home config file ---"
    elif [ -f $TESTS_DIR/cas_local_config ] ; then
        . $TESTS_DIR/cas_local_config
        echo "--- Using local config file ---"
    fi
}

start_test() {
    if [ -z $LOG_FILE ] ; then
        load_cas_lib
    else
        load_cas_lib >> $LOG_FILE
    fi
    parse_args $*
    check_config

    if [ -z $DESCRIPTION ]
    then
        DESCRIPTION=$(cat $0 | grep "# DESCRIPTION" | sed 's/# DESCRIPTION //g')
        export DESCRIPTION="$DESCRIPTION"
    fi

    STORE_CONFIG_OPTION=1 clear_config

    clear_options
    echo  "*** Starting test $0 - $DESCRIPTION ***"
    TEST_TIME="$(date +%s)"
}

end_test() {
    TEST_TIME="$(($(date +%s) - TEST_TIME))"
    echo  "*** Finished test $NAME ***"
    echo  "*** The test took $TEST_TIME seconds ***"
    echo "result : $1"

    restore_config

    if [ $1 -ne 0 ] ; then
        # Try to cleanup in case of failure - it might not always work, but could help to
        # execute remaining tests
        umount ${CORE_DEVICE}* &>/dev/null
        remove_caches
        test_log_stop
    fi
    # Delete all temporary files
    rm -rf $TMP_DIR/*
    # Delete created symlinks for NVMes
    if [[ $CACHE_DEVICE != "/dev/nvme"* ]] ; then
        exit $1
    fi

    local L_NVME_SYM_LINKS=`ls /dev/ | grep -E -w "${CACHE_DEVICE:5}[1-9][1-9]?"`

    for SYM_LINK in $L_NVME_SYM_LINKS
    do
            if [ -L "/dev/${SYM_LINK}" ]
            then
                rm -f "/dev/${SYM_LINK}"
            fi
    done

    exit $1
}

echo_green() {
    echo -e "${GREEN}$*${RESTORE}"
}

echo_red() {
    echo -e "${RED}$*${RESTORE}"
}

echo_yellow() {
    echo -e "${YELLOW}$*${RESTORE}"
}

echo_green_n() {
    echo -ne "${GREEN}$*${RESTORE}"
}

echo_red_n() {
    echo -ne "${RED}$*${RESTORE}"
}

echo_yellow_n() {
    echo -ne "${YELLOW}$*${RESTORE}"
}

success() {
    echo -n "$*"
    echo_green "[OK]"
}

error() {
    echo -n "$*"
    echo_red "[ERROR]"
}

not_run() {
    echo -n "$*"
    echo_yellow "[NOT RUN]"
}

warning() {
    echo -n "$*"
    echo_yellow "[WARNING]"
}

# This function should be used by all API wrappers and may be also used
# directly in tests. It runs the command passed to it and checks the return
# code against NEGATIVE_TEST_OPTION. If the NEGATIVE_TEST_OPTION is set, then
# we assume the command should fail - otherwise the command should succeed.
# If the output is correct, the command is printed and the test continues;
# if not, we print an error message and end the test immediately.
run_cmd() {
    export RUN_CMD_OUTPUT=""
    local L_RUN_CMD_START=$SECONDS
    if [ -z "$SILENT_COMMAND_OPTION" ] ; then
        echo -n "$(date +%Y-%m-%d_%H:%M:%S)  "
        echo -n "Running $* "
    fi
    OUTPUT=$(eval "$*" 2>&1)
    STATUS=$?
    export RUN_CMD_OUTPUT="${OUTPUT}"
    export RUN_CMD_TIME=$(( $SECONDS - $L_RUN_CMD_START ))
    if [ -z "$SILENT_COMMAND_OPTION" ] ; then
        if [ -n "$NEGATIVE_TEST_OPTION" ] && [ "$NEGATIVE_TEST_OPTION" -ne 0 ] ; then
            echo -n "(negative test) "
            if [ $STATUS -ne 0 ] ; then
                success
            else
                error
                echo "--- Command output:"
                echo "$OUTPUT"
                if [ -z $DONT_FAIL_ON_ERROR_OPTION ]; then
                    end_test 1
                fi
                return 1
            fi
        else
            if [ $STATUS -eq 0 ]  ; then
                success
            else
                error
                echo "--- Command output:"
                echo "$OUTPUT"
                if [ -z $DONT_FAIL_ON_ERROR_OPTION ]; then
                    end_test 1
                fi
                return 1
            fi
        fi
    fi
}

# This function converts size to bytes amount. It takes one parameter and
# the format of it is: [0-9][b|kB|kiB|k|M|MiB|MB|G|GiB|GB|T|TiB|TB]
#
# example of usage: BYTES=$(get_bytes 128kB)
# return          : 128*1024 = 131,072
#
get_bytes () {
    local PARAM
    local FACTOR
    local BYTES

    if [[ $1 =~ ^([0-9]*)$ ]] ; then
        PARAM=${BASH_REMATCH[1]}
        FACTOR=1
    elif [[ $1 =~ ^(b)$ ]] ; then
        PARAM=1
        FACTOR=512
    elif [[ $1 =~ ^([0-9]*)(b)$ ]] ; then
        PARAM=${BASH_REMATCH[1]}
        FACTOR=512
    elif [[ $1 =~ ^(kB)$ ]] ; then
        PARAM=1
        FACTOR=1000
    elif [[ $1 =~ ^(k|kiB)$ ]] ; then
        PARAM=1
        FACTOR=1024
    elif [[ $1 =~ ^([0-9]*)(kB)$ ]] ; then
        PARAM=${BASH_REMATCH[1]}
        FACTOR=1000
    elif [[ $1 =~ ^([0-9]*)(k|kiB)$ ]] ; then
        PARAM=${BASH_REMATCH[1]}
        FACTOR=1024
    elif [[ $1 =~ ^(MB)$ ]] ; then
        PARAM=1
        FACTOR=1000*1000
    elif [[ $1 =~ ^(M|MiB)$ ]] ; then
        PARAM=1
        FACTOR=1024*1024
    elif [[ $1 =~ ^([0-9]*)(MB)$ ]] ; then
        PARAM=${BASH_REMATCH[1]}
        FACTOR=1000*1000
    elif [[ $1 =~ ^([0-9]*)(M|MiB)$ ]] ; then
        PARAM=${BASH_REMATCH[1]}
        FACTOR=1024*1024
    elif [[ $1 =~ ^(GB)$ ]] ; then
        PARAM=1
        FACTOR=1000*1000*1000
    elif [[ $1 =~ ^(G|GiB)$ ]] ; then
        PARAM=1
        FACTOR=1024*1024*1024
    elif [[ $1 =~ ^([0-9]*)(GB)$ ]] ; then
        PARAM=${BASH_REMATCH[1]}
        FACTOR=1000*1000*1000
    elif [[ $1 =~ ^([0-9]*)(G|GiB)$ ]] ; then
        PARAM=${BASH_REMATCH[1]}
        FACTOR=1024*1024*1024
    elif [[ $1 =~ ^(T|TiB)$ ]] ; then
        PARAM=1
        FACTOR=1024*1024*1024*1024
    elif [[ $1 =~ ^(TB)$ ]] ; then
        PARAM=1
        FACTOR=1000*1000*1000*1000
    elif [[ $1 =~ ^([0-9]*)(T|TiB)$ ]] ; then
        PARAM=${BASH_REMATCH[1]}
        FACTOR=1024*1024*1024*1024
    elif [[ $1 =~ ^([0-9]*)(TB)$ ]] ; then
        PARAM=${BASH_REMATCH[1]}
        FACTOR=1000*1000*1000*1000
    else
        echo "Input parameter error, ($1) is not [0-9](b|kB|kiB|k|M|MiB|MB|G|GiB|GB|T|TiB|TB)"
        exit 1
    fi

    let BYTES=$PARAM*$FACTOR

    echo $BYTES
}

# This function returns pages amount for specified byte size. It takes
# folowing parameters:
# size - size that will be computed to number of pages
# [page_size] - optional, if ommited then page size is 4k
#
# example of usage: PAGES=$(get_bytes 128kB [$PAGE_SIZE=4k])
# return          : 128kB / $PAGE_SIZE = 32
#
get_pages () {
    local PAGES
    local PAGE_SIZE
    local BYTES

    if [ -z "$2" ]
    then
        PAGE_SIZE=4096
    else
        PAGE_SIZE=$2
    fi

    PAGE_SIZE=$(get_bytes $PAGE_SIZE)
    BYTES=$(get_bytes $1)

    let PAGES=$BYTES/$PAGE_SIZE

    echo $PAGES
}

TEST_LOG_PID="${TEST_LOG_DIR}/.test_log_pid"

# Start print log to the STD
#
# usage:
# test_log_tail <log file>
test_log_tail () {
    #
    # Check if traicing in progress
    #
    if [ -f $TEST_LOG_PID ]
    then
        local L_PID=$(ps | grep tail | grep $(cat $TEST_LOG_PID))
        if [ "" != "$L_PID" ]
        then
            return 0
        fi
    fi

    tail -f $1 &
    echo $! > $TEST_LOG_PID
    return 0
}

# Test Log Utils Function
# It shall be used in following order
# test_log_start()
#
#
# test_log_trace() # Do tracing
#
#
# test_log_stop()
#
# Start trace loging. Function takes following parameters:
# [TEST_LOG_FILE_NAME] File where log will be captured. If omitted log file name
#                      id "cas_test_log"
#
# example of usage: test_log_start [$TEST_LOG_FILE_NAME]
#
test_log_start() {
    if [ -n "$1" ]
    then
        export TEST_LOG="${TEST_LOG_DIR}/$1"
    else
        export TEST_LOG="${TEST_LOG_DIR}/cas_test_log_ext"
    fi

    if [ ! -d $TEST_LOG_DIR ] ; then
        mkdir -p $TEST_LOG_DIR
    fi
    echo >> $TEST_LOG
    echo "********************************************************" >> $TEST_LOG
    echo "* Test log start : $(date)"  >> $TEST_LOG
    echo "* $DESCRIPTION - $TEST_LOG" >> $TEST_LOG
    echo "********************************************************" >> $TEST_LOG

    if [ -z $DISABLE_EXTERNAL_LOGGING ]
    then
        test_log_tail $TEST_LOG
    fi
}

# Stop trace loging
test_log_stop() {

    if [ -n "${TEST_LOG}" ]; then
        echo "########################################################" >> $TEST_LOG
        echo "* $DESCRIPTION - $TEST_LOG" >> $TEST_LOG
        echo "# Test log stop : $(date)"  >> $TEST_LOG
        echo "########################################################" >> $TEST_LOG
        echo >> $TEST_LOG
        sleep 1
    fi

    if [ -f $TEST_LOG_PID ]
    then
        local L_PID=$(ps | grep tail | grep $(cat $TEST_LOG_PID))
        if [ "" != "$L_PID" ]
        then
            kill $(cat $TEST_LOG_PID)
            rm -f $TEST_LOG_PID
        fi
    fi
}

#
# Trace log message.
#
# example of usage: test_log_trace "Some message to store in file"
#
test_log_trace() {
    if [[ ! -z $1 ]]
    then
        echo "$DESCRIPTION : $1" >> $TEST_LOG
    fi
}

set_categories() {
     if [ "" != "$TEST_MODE_DIR" ]
     then
         export CATEGORIES=$TEST_MODE_DIR
         return 0
    fi

    local TMP_CATEGORIES="$(find $TESTS_DIR -type d | grep -v old | grep -v logs | grep -v ^.$)"
    for CATEGORY in $TMP_CATEGORIES ; do
        CATEGORIES="$(basename $CATEGORY) $CATEGORIES"
    done
    export CATEGORIES=$(echo $CATEGORIES | tr ' ' '\n' | sort | tr '\n' ' ' && echo)
}

#
# Warmup device
#
#param1 - Input
#param2 - Output
#param3 - Block Size
#param4 - Count
#param5 - Seek
#param6 - Skip
warmup() {
    local L_IF=$1
    local L_OF=$2
    local L_BS=$3
    local L_COUNT=$4
    local L_SEEK=$5
    local L_SKIP=$6
    local I_SEEK
    local I_SKIP

    if [ "$L_OF" = "/dev/null" ] ; then
        DD_CONF='if=${L_IF} of=${L_OF} count=1 bs=${L_BS} skip=$I_SKIP iflag=direct'
    else
        DD_CONF='if=${L_IF} of=${L_OF} count=1 bs=${L_BS} seek=$I_SEEK skip=$I_SKIP oflag=direct iflag=direct conv=notrunc'
    fi

    for (( I=0; I<$L_COUNT; I=I+2 ))
    do
        let I_SEEK=${L_SEEK}+${I}
        let I_SKIP=${L_SKIP}+${I}

        eval "dd $DD_CONF &>/dev/null"
        if [ $? != 0 ]
        then
            return 1
        fi
    done

    for (( I=1; I<$L_COUNT; I=I+2 ))
    do
        let I_SEEK=${L_SEEK}+${I}
        let I_SKIP=${L_SKIP}+${I}

        eval "dd $DD_CONF &>/dev/null"
        if [ $? != 0 ]
        then
            return 1
        fi
    done

    return 0
}

cas_version () {
    local L_CAS_VERSION=$(LANG=1 casadm -V -o csv 2>/dev/null | grep "CLI" | cut -d ',' -f2)
    echo $L_CAS_VERSION
}

check_if_root_or_exit
